package filefinder;

import java.io.File;
import java.io.IOException;
import java.util.List;

import thread.ThreadPool;
import filefinder.finders.Finder;

/**
 * Performs recursive file search for the specified string
 * 
 * @author denist
 * 
 */
public class FileFinder {
	
	private final ThreadPool pool;

	/**
	 * Creates instance with specified pool size
	 * 
	 * @param poolSize
	 *            size of pool
	 */
	public FileFinder(int poolSize) {
		pool = ThreadPool.newFixedSizeThreadPool(poolSize);
	}

	/**
	 * Request shutdown of the <code>FileFinder</code> and <br>
	 * Blocks calling thread until all work assigned is finished
	 */
	public void waitForResult() {
		pool.shutdown();
	}

	/**
	 * Recursively traverse the directory tree and assign search tasks to thread
	 * pool, gathering the result of the search in the provided list
	 * 
	 * @param root
	 *            start point of the search
	 * @param toFind
	 *            string to search for
	 * @param list
	 *            files in which search string found will be added to this list
	 * @param finder
	 *            concrete implementation of finder (Grep:
	 *            <code>{@link filefinder.finders#GrepFinder}</code> Channel:
	 *            <code>{@link filefinder.finders#ChannelGrepFinder}</code>
	 *            String: <code>{@link filefinder.finders#StringFinder}</code> )
	 */
	public void findFile(final File root, final String toFind, final List list,
			final Finder finder) {

		paramsValidation(root, toFind, list);

		if (isSymbolicLink(root)) {
			return;
		}

		if (!root.isDirectory()) {
			performSearchInFile(root, toFind, list, finder);
			return;
		}

		performSearchInDirectory(root, toFind, list, finder);
	}

	private void performSearchInDirectory(final File root, final String toFind,
			final List list, final Finder finder) {
		
		File[] files = root.listFiles();

		if (files == null) { //Can be null in case of IO error
			System.out.println(root + " has null root.listFiles()");
			return;
		}

		for (int i = 0; i < files.length; i++) {
			final File file = files[i];
			if (file.isDirectory()) {
				findFile(file, toFind, list, finder);
			} else {
				performSearchInFile(file, toFind, list, finder);
			}
		}
	}

	private boolean isSymbolicLink(final File root) {
		try {
			if (!root.getCanonicalPath().equals(root.getAbsolutePath())) {
				System.out.println(root.getCanonicalPath() + ", "
						+ root.getAbsolutePath()
						+ ", (not processing symbolics links in this version)");
				return true;
			}
		} catch (IOException e) {
			System.out.println("Cannot get canonical file path due to");
			e.printStackTrace();
		}
		return false;
	}

	private void paramsValidation(final File root, final String toFind,
			final List list) {
		if (root == null)
			throw new IllegalArgumentException("Root cannot be null");
		if (toFind == null)
			throw new IllegalArgumentException("String cannot be null");
		if (list == null)
			throw new IllegalArgumentException("List cannot be null");
	}

	private void performSearchInFile(final File file, final String toFind,
			final List list, final Finder finder) {
		if (!file.isFile()) {
			System.out.println(file + " (Not a file) ");
			return;
		}
		if (file.canRead()) {
			Runnable task = new Runnable() {
				public void run() {
					finder.find(file, toFind, list);
				}
			};
			pool.execute(task);
		} else {
			System.out.println(file + " (Access is denied) ");
		}
	}
}
